cormoran.me


cormoran



Recent Post





clang のSanitizerについて

昔調べた時にclangで-fsanitize=undefinedが使えなかったので、よく考えずにaddressを使っていた。新しいclangを入れようとしていろいろしていたら使えるようになったので、ついでにsanitizer周りについて調べてみた。

環境

sanitizerはどれが使えるかとかが結構環境(OS)依存なようなので重要

  • Mac OS X El Capitan (10.11.6)

  • clang 3.8.0 (brew install llvm38 --with-asan --with-lldb)

    --with-lldbは関係ないかもしれないけど、一応上のコマンドで入れた。--with-asanが重要

概要

clangで使えるsanitizerにはAddressSanitizer,ThreadSanitizer,MemorySanitizer,UndefinedBehaviorSanitizerなどがある。

コンパイル時に

clang++ -fsanitize=address hoo.cpp

のように-fsanitize=hogeとつけると、実行ファイルにsanitizerがリンクされて実行時の各種エラーを見つけてくれる。

Memory, Thread SAnitizerはMacをサポートしていないようだったのでAddressSanitizerとUndefinedBehaviorSanitizerについて書く。

AddressSanitizer

Xcodeで入ってくるclangでも使えた。

メモリ関連のバグを実行時に検出してくれる。内容はclangのドキュメントを見るのがわかりやすいが、ざっくりまとめると、

  • 以下のようなエラーを検出する
    • head, stack, globalでの領域外アクセス
    • freeした領域の使用
    • return した後のstackの利用 (追加オプション)
    • Use-after-scope (追加オプション)
    • 多重free, 無効なfree
    • メモリリーク (experimental)
  • 実行速度は2倍くらい遅くなる
  • スタックメモリは3倍くらい使う

ありそうな配列外アクセス

// array-out-of-bound.cpp
int main() {
    int a[100];
    for(int i = 0; i <= 100; i++) a[i] = 100;
    return 0;
}

自分は使いこなせていないが、周辺のメモリの状態まで表示してくれる。

$ clang++-3.8 -fsanitize=address array-out-of-bound.cpp
$ ./a.out
=================================================================
==83804==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fff5cd683b0 at pc 0x000102e97e36 bp 0x7fff5cd681f0 sp 0x7fff5cd681e8
WRITE of size 4 at 0x7fff5cd683b0 thread T0
    #0 0x102e97e35 in main (a.out+0x100000e35)
    #1 0x7fff8a4965ac in start (libdyld.dylib+0x35ac)
    #2 0x0  (<unknown module>)

Address 0x7fff5cd683b0 is located in stack of thread T0 at offset 432 in frame
    #0 0x102e97cef in main (a.out+0x100000cef)

  This frame has 1 object(s):
    [32, 432) 'a' <== Memory access at offset 432 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow (a.out+0x100000e35) in main
Shadow bytes around the buggy address:
  0x1fffeb9ad020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1fffeb9ad030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1fffeb9ad040: f1 f1 f1 f1 00 00 00 00 00 00 00 00 00 00 00 00
  0x1fffeb9ad050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1fffeb9ad060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x1fffeb9ad070: 00 00 00 00 00 00[f3]f3 f3 f3 f3 f3 f3 f3 f3 f3
  0x1fffeb9ad080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1fffeb9ad090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1fffeb9ad0a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1fffeb9ad0b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1fffeb9ad0c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==83804==ABORTING
[3]    83804 abort      ./a.out

また、-gをつけて、デバッグ情報付きでコンパイルすると以下のように、a.out+hogeだった部分がソースファイル名とエラーが出た行番号になる。

    #0 0x105f92e35 in main array-out-of-bound.cpp:8
    #1 0x7fff8a4965ac in start (libdyld.dylib+0x35ac)
    #2 0x0  (<unknown module>)

Address 0x7fff59c6d3b0 is located in stack of thread T0 at offset 432 in frame
    #0 0x105f92cef in main array-out-of-bound.cpp:6

UndefinedBehaviorSanitizer

Xcodeで入ってきたclangでは使えなかった。brew install llvm38 --with-asan --with-lldbで入れたclangでは使えた。しかし、--with-asanはaddress sanitizerを入れるオプションなので、関連性は知らない。

実行中に様々な未定義動作を検出して教えてくれる。AddressSanitizerと違って、プログラムはcrashせず動き続ける。(オプションによる)

チェックしてくれる項目はclangのドキュメント参照。-fsanitize=undefinedでunsigned-integer-overflow 以外が全て有効になる。

例1

さっきのプログラムに最後の出力を加えたもの

// array-out-of-bound.cpp
#include<iostream>
int main() {
    int a[100];
    for(int i = 0; i <= 100; i++) a[i] = 100;
    std::cout << "Finish" << std::endl;
    return 0;
}

エラーを表示しながらも、プログラムは動き続けている。

$ clang++-3.8 -fsanitize=undefined array-out-of-bound.cpp
$ ./a.out
array-out-of-bound.cpp:9:35: runtime error: index 100 out of bounds for type 'int [100]'
Finish

例2

オーバーフロー、アンダーフローの例

#include<climits>
int main() {
    int a = INT_MAX;
    a += 1;
    a -= 1;
    a -= INT_MAX;
    return 0;
}

嬉しい

$ clang++-3.8 -fsanitize=undefined integer-overflow.cpp
$ ./a.out
integer-overflow.cpp:8:7: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
integer-overflow.cpp:9:7: runtime error: signed integer overflow: -2147483648 - 1 cannot be represented in type 'int'

組み合わせる

AddressSanitizerとUndefinedBehaviorSanitizerは両方同時に使うことができる。

ただし、Address, Thread, Memoryはどれか1つしか同時に使えない。

// array-out-of-bound.cpp
#include<iostream>
int main() {
    int a[100];
    for(int i = 0; i <= 100; i++) a[i] = 100;
    std::cout << "Finish" << std::endl;
    return 0;
}

以下のように、未定義動作をruntime errorで表示しつつ、領域外アクセスなどがあったらabortして詳細を表示してくれる。

undefinedが結構強いので、addressはつけなくても十分かもしれない?

$ clang++-3.8 -fsanitize=undefined,address array-out-of-bound.cpp
$ ./a.out
array-out-of-bound.cpp:9:35: runtime error: index 100 out of bounds for type 'int [100]'
SUMMARY: AddressSanitizer: undefined-behavior array-out-of-bound.cpp:9:35 in
=================================================================
==19791==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fff5acce370 at pc 0x000104f31b20 bp 0x7fff5acce1b0 sp 0x7fff5acce1a8
WRITE of size 4 at 0x7fff5acce370 thread T0
    #0 0x104f31b1f in main (a.out+0x100000b1f)
    #1 0x7fff8a4965ac in start (libdyld.dylib+0x35ac)
    #2 0x0  (<unknown module>)

Address 0x7fff5acce370 is located in stack of thread T0 at offset 432 in frame
    #0 0x104f3194f in main (a.out+0x10000094f)

  This frame has 1 object(s):
    [32, 432) 'a' <== Memory access at offset 432 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow (a.out+0x100000b1f) in main
Shadow bytes around the buggy address:
  0x1fffeb599c10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1fffeb599c20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1fffeb599c30: 00 00 00 00 00 00 00 00 f1 f1 f1 f1 00 00 00 00
  0x1fffeb599c40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1fffeb599c50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x1fffeb599c60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00[f3]f3
  0x1fffeb599c70: f3 f3 f3 f3 f3 f3 f3 f3 00 00 00 00 00 00 00 00
  0x1fffeb599c80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1fffeb599c90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1fffeb599ca0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1fffeb599cb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==19791==ABORTING
[3]    19791 abort      ./a.out